bitkeeper revision 1.1662.1.18 (42a5a348BpHwgoiWQI1wI9LMej7F3w)
authorcl349@firebug.cl.cam.ac.uk <cl349@firebug.cl.cam.ac.uk>
Tue, 7 Jun 2005 13:38:16 +0000 (13:38 +0000)
committercl349@firebug.cl.cam.ac.uk <cl349@firebug.cl.cam.ac.uk>
Tue, 7 Jun 2005 13:38:16 +0000 (13:38 +0000)
setup.py:
  Add xen.xend.xenstore.
xsresource.py, xsobj.py, xsnode.py, __init__.py:
  new file
Signed-off-by: Mike Wray <mike.wray@hp.com>
Signed-off-by: Christian Limpach <Christian.Limpach@cl.cam.ac.uk>
.rootkeys
tools/python/setup.py
tools/python/xen/xend/xenstore/__init__.py [new file with mode: 0644]
tools/python/xen/xend/xenstore/xsnode.py [new file with mode: 0644]
tools/python/xen/xend/xenstore/xsobj.py [new file with mode: 0644]
tools/python/xen/xend/xenstore/xsresource.py [new file with mode: 0644]

index 7aceab35997723d5d8578b8d169c640033b0d851..ae7543d74cf158f9166b9f0038b716ef5cbd39fb 100644 (file)
--- a/.rootkeys
+++ b/.rootkeys
 41ee5e8dq9NtihbL4nWKjuSLOhXPUg tools/python/xen/xend/server/usbif.py
 40c9c469LNxLVizOUpOjEaTKKCm8Aw tools/python/xen/xend/sxp.py
 42a48d152jkT7ykQT_LWKnS-ojV_ZA tools/python/xen/xend/uuid.py
+42a5a2c0ik9zrQvwjTUKDVVEQmvO2Q tools/python/xen/xend/xenstore/__init__.py
+42a5a2c04xNCYAUXD0b9IDf4XekXRg tools/python/xen/xend/xenstore/xsnode.py
+42a5a2c0-aP98db2PJIDxQJfTEMZ-A tools/python/xen/xend/xenstore/xsobj.py
+42a5a2c0gxfQiAH_oVTShNPeG0LG2Q tools/python/xen/xend/xenstore/xsresource.py
 40d05079aFRp6NQdo5wIh5Ly31c0cg tools/python/xen/xm/__init__.py
 40cf2937gKQcATgXKGtNeWb1PDH5nA tools/python/xen/xm/create.py
 40f552eariuUSB9TWqCPnDLz5zvxMw tools/python/xen/xm/destroy.py
index 5902d4151f267014b2c3e121638eb4181f910a74..fabe80bd8bceaf53172a8ed6037ff2a7bf8ca816 100644 (file)
@@ -48,6 +48,7 @@ setup(name            = 'xen',
                          'xen.util',
                          'xen.xend',
                          'xen.xend.server',
+                         'xen.xend.xenstore',
                          'xen.xm',
                          'xen.web',
                          ],
diff --git a/tools/python/xen/xend/xenstore/__init__.py b/tools/python/xen/xend/xenstore/__init__.py
new file mode 100644 (file)
index 0000000..6772d2c
--- /dev/null
@@ -0,0 +1,2 @@
+from xsnode import *
+from xsobj import *
diff --git a/tools/python/xen/xend/xenstore/xsnode.py b/tools/python/xen/xend/xenstore/xsnode.py
new file mode 100644 (file)
index 0000000..ae77021
--- /dev/null
@@ -0,0 +1,382 @@
+import errno
+import os
+import os.path
+import select
+import sys
+import time
+
+from xen.lowlevel import xs
+from xen.xend import sxp
+from xen.xend.PrettyPrint import prettyprint
+
+SELECT_TIMEOUT = 2.0
+
+def getEventPath(event):
+    return os.path.join("/_event", event)
+
+def getEventIdPath(event):
+    return os.path.join(eventPath(event), "@eid")
+
+class Subscription:
+
+    def __init__(self, event, fn, id):
+        self.event = event
+        self.watcher = None
+        self.fn = fn
+        self.id = id
+
+    def watch(self, watcher):
+        self.watcher = watcher
+        watcher.addSubs(self)
+
+    def unwatch(self):
+        watcher = self.watcher
+        if watcher:
+            self.watcher = None
+            watcher.delSubs(self)
+
+    def notify(self, event):
+        try:
+            self.fn(event, id)
+        except SystemExitException:
+            raise
+        except:
+            pass
+
+class Watcher:
+
+    def __init__(self, store, event):
+        self.path = getEventPath(event)
+        self.eidPath = getEventIdPath(event)
+        store.mkdirs(self.path)
+        if not store.exists(self.eidPath):
+            store.writeInt(self.eidPath, 0)
+        self.xs = None
+        self.subs = []
+
+    def __getattr__(self, k, v):
+        if k == "fileno":
+            if self.xs:
+                return self.xs.fileno
+            else:
+                return -1
+        else:
+            return self.__dict__.get(k, v)
+
+    def addSubs(self, subs):
+        self.subs.append(subs)
+        self.watch()
+
+    def delSubs(self, subs):
+        self.subs.remove(subs)
+        if len(self.subs) == 0:
+            self.unwatch()
+
+    def getEvent(self):
+        return self.event
+
+    def watch(self):
+        if self.xs: return
+        self.xs = xs.open()
+        self.xs.watch(path)
+
+    def unwatch(self):
+        if self.xs:
+            self.xs.unwatch(self.path)
+            self.xs.close()
+            self.xs = None
+            
+    def watching(self):
+        return self.xs is not None
+
+    def getNotification(self):
+        p = self.xs.read_watch()
+        self.xs.acknowledge_watch()
+        eid = self.xs.readInt(self.eidPath)
+        return p
+
+    def notify(self, subs):
+        p = self.getNotification()
+        for s in subs:
+            s.notify(p)
+            
+class XenStore:
+
+    def __init__(self):
+        self.xs = None
+        #self.xs = xs.open()
+        self.subscription = {}
+        self.subscription_id = 0
+        self.events = {}
+        self.write("/", "")
+
+    def getxs(self):
+        if self.xs is None:
+            ex = None
+            for i in range(0,20):
+                try:
+                    self.xs = xs.open()
+                    ex = None
+                    break
+                except Exception, ex:
+                    print >>stderr, "Exception connecting to xsdaemon:", ex
+                    print >>stderr, "Trying again..."
+                    time.sleep(1)
+            else:
+                raise ex
+            
+        #todo would like to reconnect if xs conn closes (e.g. daemon restart).
+        return self.xs
+
+    def dump(self, path="/", out=sys.stdout):
+        print 'dump>', path
+        val = ['node']
+        val.append(['path',  path])
+##         perms = ['perms']
+##         for p in self.getPerms(path):
+##             l = ['perm']
+##             l.append('dom', p.get['dom'])
+##             for k in ['read', 'write', 'create', 'owner']:
+##                 v = p.get(k)
+##                 l.append([k, v])
+##             perms.append(l)
+##         val.append(perms)
+        data = self.read(path)
+        if data:
+            val.append(['data',  data])
+        children = ['children']
+        for x in self.lsPaths(path):
+            print 'dump>', 'child=', x
+            children.append(self.dump(x))
+        if len(children) > 1:
+            val.append(children)
+        prettyprint(val, out=out)
+        return val
+
+    def getPerms(self, path):
+        return self.getxs().get_permissions(path)
+
+    def ls(self, path="/"):
+        return self.getxs().ls(path)
+
+    def lsPaths(self, path="/"):
+        return [ os.path.join(path, x) for x in self.ls(path) ]
+
+    def lsr(self, path="/", list=None):
+        if list is None:
+            list = []
+        list.append(path)
+        for x in self.lsPaths(path):
+            list.append(x)
+            self.lsr(x, list=list)
+        return list
+
+    def rm(self, path):
+        try:
+            #for x in self.lsPaths():
+            #    self.getxs().rm(x)
+            self.getxs().rm(path)
+        except:
+            pass
+
+    def exists(self, path):
+        try:
+            self.getxs().ls(path)
+            return True
+        except RuntimeError, ex:
+            if ex.args[0] == errno.ENOENT:
+                return False
+            else:
+                raise
+
+    def mkdirs(self, path):
+        if self.exists(path):
+            return
+        elts = path.split("/")
+        p = "/"
+        for x in elts:
+            if x == "": continue
+            p = os.path.join(p, x)
+            if not self.exists(p):
+                self.getxs().write(p, "", create=True)
+
+    def read(self, path):
+        try:
+            return self.getxs().read(path)
+        except RuntimeError, ex:
+            if ex.args[0] == errno.EISDIR:
+                return None
+            else:
+                raise
+
+    def create(self, path, excl=False):
+        self.write(path, "", create=True, excl=excl)
+
+    def write(self, path, data, create=True, excl=False):
+        self.mkdirs(path)
+        self.getxs().write(path, data, create=create, excl=excl)
+
+    def begin(self, path):
+        self.getxs().begin_transaction(path)
+
+    def commit(self, abandon=False):
+        self.getxs().end_transaction(abort=abandon)
+
+    def subscribe(self, event, fn):
+        watcher = self.watchEvent(event)
+        self.subscription_id += 1
+        subs = Subscription(event, fn, self.subscription_id)
+        self.subscription[subs.id] = subs
+        subs.watch(watcher)
+        return subs.id
+
+    def unsubscribe(self, sid):
+        s = self.subscription.get(sid)
+        if not s: return
+        del self.subscription[s.id]
+        s.unwatch()
+        unwatchEvent(s.event)
+
+    def sendEvent(self, event, data):
+        eventPath = getEventPath(event)
+        eidPath = getEventIdPath(event)
+        try:
+            self.begin(eventPath)
+            self.mkdirs(eventPath)
+            if self.exists(eidPath):
+                eid = self.readInt(eidPath)
+                eid += 1
+            else:
+                eid = 1
+            self.writeInt(eidPath, eid)
+            self.write(os.path.join(eventPath, str(eid)), data)
+        finally:
+            self.commit()
+
+    def watchEvent(self, event):
+        if event in  self.events:
+            return
+        watcher = Watcher(event)
+        self.watchers[watcher.getEvent()] = watcher
+        self.watchStart()
+        return watcher
+
+    def unwatchEvent(self, event):
+        watcher = self.watchers.get(event)
+        if not watcher:
+            return
+        if not watcher.watching():
+            del self.watchers[event]
+
+    def watchStart(self):
+        if self.watchThread: return
+
+    def watchMain(self):
+        try:
+            while True:
+                if self.watchThread is None: return
+                if not self.events:
+                    return
+                rd = self.watchers.values()
+                try:
+                    (rd, wr, er) = select.select(rd, [], [], SELECT_TIMEOUT)
+                    for watcher in rd:
+                        watcher.notify()
+                except socket.error, ex:
+                    if ex.args[0] in (EAGAIN, EINTR):
+                        pass
+                    else:
+                        raise
+        finally:
+            self.watchThread = None
+
+    def introduceDomain(self, dom, page, evtchn, path):
+        self.getxs().introduce_domain(dom, page, evtchn.port1, path)
+
+    def releaseDomain(self, dom):
+        self.getxs().release_domain(dom)
+
+def getXenStore():
+    global xenstore
+    try:
+        return xenstore
+    except:
+        xenstore = XenStore()
+        return xenstore
+
+class XenNode:
+
+    def __init__(self, path="/", create=True):
+        self.store = getXenStore()
+        self.path = path
+        if not self.store.exists(path):
+            if create:
+                self.store.create(path)
+            else:
+                raise ValueError("path does not exist: '%s'" % path)
+
+    def relPath(self, path=""):
+        if not path:
+            return self.path
+        if path and path.startswith("/"):
+            path = path[1:]
+        return os.path.join(self.path, path)
+
+    def delete(self, path=""):
+        self.store.rm(self.relPath(path))
+
+    def exists(self, path=""):
+        return self.store.exists(self.relPath(path))
+
+    def getNode(self, path="", create=True):
+        if path == "":
+            return self
+        else:
+            return XenNode(self.relPath(path=path), create=create)
+
+    getChild = getNode
+
+    def getData(self, path=""):
+        path = self.relPath(path)
+        try:
+            return self.store.read(path)
+        except:
+            return None
+
+    def setData(self, data, path=""):
+        path = self.relPath(path)
+        #print 'XenNode>setData>', 'path=', path, 'data=', data
+        return self.store.write(path, data)
+
+    def getLock(self):
+        return None
+
+    def lock(self, lockid):
+        return None
+
+    def unlock(self, lockid):
+        return None
+
+    def deleteChild(self, name):
+        self.delete(name)
+
+    def deleteChildren(self):
+        for name in self.ls():
+            self.deleteChild(name)
+
+    def getChildren(self):
+        return [ self.getNode(name) for name in self.ls() ]
+
+    def ls(self):
+        return self.store.ls(self.path)
+
+    def introduceDomain(self, dom, page, evtchn, path):
+        self.store.introduceDomain(dom, page, evtchn, path)
+        
+    def releaseDomain(self, dom):
+        self.store.releaseDomain(dom)
+
+    def __repr__(self):
+        return "<XenNode %s>" % self.path
+
+
diff --git a/tools/python/xen/xend/xenstore/xsobj.py b/tools/python/xen/xend/xenstore/xsobj.py
new file mode 100644 (file)
index 0000000..62e18d0
--- /dev/null
@@ -0,0 +1,519 @@
+import string
+import types
+
+from xen.xend import sxp
+from xsnode import XenNode
+from xen.util.mac import macToString, macFromString
+
+VALID_KEY_CHARS = string.ascii_letters + string.digits + "_-@"
+
+def hasAttr(obj, attr):
+    if isinstance(obj, dict):
+        return obj.contains(attr)
+    else:
+        return hasattr(obj, attr)
+
+def getAttr(obj, attr):
+    if isinstance(obj, dict):
+        return dict.get(attr)
+    else:
+        return getattr(obj, attr, None)
+
+def setAttr(obj, attr, val):
+    if isinstance(obj, dict):
+        dict[attr] = val
+    else:
+        setattr(obj, attr, val)
+
+class DBConverter:
+    """Conversion of values to and from strings in xenstore.
+    """
+
+    converters = {}
+
+    def checkType(cls, ty):
+        if ty is None or ty in cls.converters:
+            return
+        raise ValueError("invalid converter type: '%s'" % ty)
+
+    checkType = classmethod(checkType)
+    
+    def getConverter(cls, ty=None):
+        if ty is None:
+            ty = "str"
+        conv = cls.converters.get(ty)
+        if not conv:
+            raise ValueError("no converter for type: '%s'" % ty)
+        return conv
+
+    getConverter = classmethod(getConverter)
+
+    def convertToDB(cls, val, ty=None):
+        return cls.getConverter(ty).toDB(val)
+
+    convertToDB = classmethod(convertToDB)
+
+    def convertFromDB(cls, val, ty=None):
+        return cls.getConverter(ty).fromDB(val)
+
+    convertFromDB = classmethod(convertFromDB)
+
+    # Must define in subclass.
+    name = None
+
+    def __init__(self):
+        self.register()
+    
+    def register(self):
+        if not self.name:
+            raise ValueError("invalid converter name: '%s'" % self.name)
+        self.converters[self.name] = self
+
+    def toDB(self, val):
+        raise NotImplementedError()
+
+    def fromDB(self, val):
+        raise NotImplementedError()
+
+class StrConverter(DBConverter):
+
+    name = "str"
+    
+    def toDB(self, val):
+        # Convert True/False to 1/0, otherwise they convert to
+        # 'True' and 'False' rather than '1' and '0', even though
+        # isinstance(True/False, int) is true.
+        if isinstance(val, bool):
+            val = int(val)
+        return str(val)
+
+    def fromDB(self, data):
+        return data
+
+StrConverter()
+    
+class BoolConverter(DBConverter):
+
+    name = "bool"
+
+    def toDB(self, val):
+        return str(int(bool(val)))
+
+    def fromDB(self, data):
+        return bool(int(data))
+
+BoolConverter()
+
+class SxprConverter(DBConverter):
+
+    name = "sxpr"
+    
+    def toDB(self, val):
+        return sxp.to_string(val)
+
+    def fromDB(self, data):
+        return sxp.from_string(data)
+    
+SxprConverter()
+
+class IntConverter(DBConverter):
+
+    name = "int"
+    
+    def toDB(self, val):
+        return str(int(val))
+
+    def fromDB(self, data):
+        return int(data)
+    
+IntConverter()
+    
+class FloatConverter(DBConverter):
+
+    name = "float"
+    
+    def toDB(self, val):
+        return str(float(val))
+
+    def fromDB(self, data):
+        return float(data)
+    
+FloatConverter()
+    
+class LongConverter(DBConverter):
+
+    name = "long"
+    
+    def toDB(self, val):
+        return str(long(val))
+
+    def fromDB(self, data):
+        return long(data)
+    
+LongConverter()
+
+class MacConverter(DBConverter):
+
+    name = "mac"
+    
+    def toDB(self, val):
+        return macToString(val)
+
+    def fromDB(self, data):
+        return macFromString(data)
+    
+MacConverter()
+
+class DBVar:
+
+    def __init__(self, var, ty=None, path=None):
+        DBConverter.checkType(ty)
+        if path is None:
+            path = var
+        self.var = var
+        self.ty = ty
+        self.path = path
+        varpath = filter(bool, self.var.split())
+        self.attrpath = varpath[:-1]
+        self.attr = varpath[-1]
+
+    def exportToDB(self, db, obj):
+        self.setDB(db, self.getObj(obj))
+
+    def importFromDB(self, db, obj):
+        self.setObj(obj, self.getDB(db))
+
+    def getObj(self, obj):
+        o = obj
+        for x in self.attrpath:
+            o = getAttr(o, x)
+            if o is None:
+                return None
+        return getAttr(o, self.attr)
+
+    def setObj(self, obj, val):
+        o = obj
+        for x in self.attrpath:
+            o = getAttr(o, x)
+        # Don't set obj attr if val is None.
+        if val is None and hasAttr(o, self.attr):
+            return
+        setAttr(o, self.attr, val)
+
+    def getDB(self, db):
+        data = getattr(db, self.path)
+        return DBConverter.convertFromDB(data, ty=self.ty)
+
+    def setDB(self, db, val):
+        # Don't set in db if val is None.
+        #print 'DBVar>setDB>', self.path, 'val=', val
+        if val is None:
+            return
+        data = DBConverter.convertToDB(val, ty=self.ty)
+        #print 'DBVar>setDB>', self.path, 'data=', data
+        setattr(db, self.path, data)
+        
+
+class DBMap(dict):
+    """A persistent map. Extends dict with persistence.
+    Set and get values using the usual map syntax:
+
+    m[k],     m.get(k)
+    m[k] = v
+
+    Also supports being treated as an object with attributes.
+    When 'k' is a legal identifier you may also use
+
+    m.k,     getattr(m, k)
+    m.k = v, setattr(m, k)
+    k in m,  hasattr(m, k)
+
+    When setting you can pass in a normal value, for example
+
+    m.x = 3
+
+    Getting works too:
+
+    m.x ==> 3
+
+    while m['x'] will return the map for x.
+
+    m['x'].getData() ==> 3
+    
+    To get values from subdirs use get() to get the subdir first:
+
+    get(m, 'foo').x
+    m['foo'].x
+
+    instead of m.foo.x, because m.foo will return the data for field foo,
+    not the directory.
+
+    You can assign values into a subdir by passing a map:
+
+    m.foo = {'x': 1, 'y':2 }
+
+    You can also use paths as keys:
+
+    m['foo/x'] = 1
+
+    sets field x in subdir foo.
+    
+    """
+
+    __db__          = None
+    __data__        = None
+    __perms__       = None
+    __parent__      = None
+    __name__        = ""
+
+    __transaction__ = False
+
+    # True if value set since saved (or never saved).
+    __dirty__       = True
+
+    def __init__(self, parent=None, name="", db=None):
+        if parent is None:
+            self.__name__ = name
+        else:
+            if not isinstance(parent, DBMap):
+                raise ValueError("invalid parent")
+            self.__parent__ = parent
+            self.__name__ = name
+            db = self.__parent__.getChildDB(name)
+        self.setDB(db)
+
+    def getName(self):
+        return self.__name__
+
+    def getPath(self):
+        return self.__db__ and self.__db__.relPath()
+
+    def introduceDomain(self, dom, page, evtchn, path=None):
+        db = self.__db__
+        if path is None:
+            path = db.relPath()
+        print 'DBMap>introduceDomain>', dom, page, evtchn, path
+        try:
+            db.introduceDomain(dom, page, evtchn, path)
+        except Exception, ex:
+            import traceback
+            traceback.print_exc()
+            print 'DBMap>introduceDomain>', ex
+            pass # todo: don't ignore
+        
+    def releaseDomain(self, dom):
+        db = self.__db__
+        print 'DBMap>releaseDomain>', dom
+        try:
+            db.releaseDomain(dom)
+        except Exception, ex:
+            import traceback
+            traceback.print_exc()
+            print 'DBMap>releaseDomain>', ex
+            pass # todo: don't ignore
+        
+    def transactionBegin(self):
+        # Begin a transaction.
+        pass
+
+    def transactionCommit(self):
+        # Commit writes to db.
+        pass
+
+    def transactionFail(self):
+        # Fail a transaction.
+        # We have changed values, what do we do?
+        pass
+
+    def watch(self, fn):
+        pass
+
+    def unwatch(self, watch):
+        pass
+        
+    def checkName(self, k):
+        if k == "":
+            raise ValueError("invalid key, empty string")
+        for c in k:
+            if c in VALID_KEY_CHARS: continue
+            raise ValueError("invalid key char '%s'" % c)
+
+    def _setData(self, v):
+        #print 'DBMap>_setData>', self.getPath(), 'data=', v
+        if v != self.__data__:
+            self.__dirty__ = True
+        self.__data__ = v
+
+    def setData(self, v):
+        if isinstance(v, dict):
+            for (key, val) in v.items():
+                self[key] = val
+        else:
+            self._setData(v)
+
+    def getData(self):
+        return self.__data__
+
+    def _set(self, k, v):
+        dict.__setitem__(self, k, v)
+
+    def _get(self, k):
+        try:
+            return dict.__getitem__(self, k)
+        except:
+            return None
+
+    def _del(self, k, v):
+        try:
+            dict.__delitem__(self, k)
+        except:
+            pass
+
+    def _contains(self, k):
+        return dict.__contains__(self, k)
+        
+    def __setitem__(self, k, v, save=False):
+        node = self.addChild(k)
+        node.setData(v)
+        if save:
+            node.saveDB()
+
+    def __getitem__(self, k):
+        if self._contains(k):
+            v = self._get(k)
+        else:
+            v = self.readChildDB(k)
+            self._set(k, v)
+        return v
+
+    def __delitem__(self, k):
+        self._del(k)
+        self.deleteChildDB(k)
+
+    def __repr__(self):
+        if len(self):
+            return dict.__repr__(self)
+        else:
+            return repr(self.__data__)
+
+    def __setattr__(self, k, v):
+        if k.startswith("__"):
+            object.__setattr__(self, k, v)
+        else:
+            self.__setitem__(k, v, save=True)
+        return v
+    
+    def __getattr__(self, k):
+        if k.startswith("__"):
+            v = object.__getattr__(self, k)
+        else:
+            try:
+                v = self.__getitem__(k).getData()
+            except LookupError, ex:
+                raise AttributeError(ex.args)
+        return v
+
+    def __delattr__(self, k):
+        return self.__delitem__(k)
+
+    def delete(self):
+        dict.clear(self)
+        self.__data__ = None
+        if self.__db__:
+            self.__db__.delete()
+
+    def clear(self):
+        dict.clear(self)
+        if self.__db__:
+            self.__db__.deleteChildren()
+
+    def getChild(self, k):
+        return self._get(k)
+
+    def getChildDB(self, k):
+        self.checkName(k)
+        return self.__db__ and self.__db__.getChild(k)
+
+    def deleteChildDB(self, k):
+        if self.__db__:
+            self.__db__.deleteChild(k)
+
+    def _addChild(self, k):
+        kid = self._get(k)
+        if kid is None:
+            kid = DBMap(parent=self, name=k, db=self.getChildDB(k))
+            self._set(k, kid)
+        return kid
+
+    def addChild(self, path):
+        l = path.split("/")
+        n = self
+        for x in l:
+            if x == "": continue
+            n = n._addChild(x)
+        return n
+
+    def setDB(self, db):
+        if (db is not None) and not isinstance(db, XenNode):
+            raise ValueError("invalid db")
+        self.__db__ = db
+        for (k, v) in self.items():
+            if v is None: continue
+            if isinstance(v, DBMap):
+                v._setDB(self.addChild(k), restore)
+
+    def readDB(self):
+        if self.__db__ is None:
+            return
+        self.__data__ = self.__db__.getData()
+        for k in self.__db__.ls():
+            n = self.addChild(k)
+            n.readDB()
+        self.__dirty__ = False
+
+    def readChildDB(self, k):
+        if self.__db__ and (k in self.__db__.ls()):
+            n = self.addChild(k)
+            n.readDB()
+        raise LookupError("invalid key '%s'" % k)
+
+    def saveDB(self, sync=False, save=False):
+        """Save unsaved data to db.
+        If save or sync is true, saves whether dirty or not.
+        If sync is true, removes db entries not in the map.
+        """
+        
+        if self.__db__ is None:
+            #print 'DBMap>saveDB>',self.getPath(), 'no db'
+            return
+        # Write data.
+        #print 'DBMap>saveDB>', self.getPath(), 'dirty=', self.__dirty__, 'data=', self.__data__
+        if ((self.__data__ is not None)
+            and (sync or save or self.__dirty__)):
+            self.__db__.setData(self.__data__)
+            self.__dirty__ = False
+        else:
+            #print 'DBMap>saveDB>', self.getPath(), 'not written'
+            pass
+        # Write children.
+        for (name, node) in self.items():
+            if not isinstance(node, DBMap): continue
+            node.saveDB(sync=sync, save=save)
+        # Remove db nodes not in children.
+        if sync:
+            for name in self.__db__.ls():
+                if name not in self:
+                    self.__db__.delete(name)
+
+    def importFromDB(self, obj, fields):
+        """Set fields in obj from db fields.
+        """
+        for f in fields:
+            f.importFromDB(self, obj)
+
+    def exportToDB(self, obj, fields, save=False, sync=False):
+        """Set fields in db from obj fields.
+        """
+        for f in fields:
+            f.exportToDB(self, obj)
+        self.saveDB(save=save, sync=sync)
diff --git a/tools/python/xen/xend/xenstore/xsresource.py b/tools/python/xen/xend/xenstore/xsresource.py
new file mode 100644 (file)
index 0000000..37011bd
--- /dev/null
@@ -0,0 +1,136 @@
+#============================================================================
+# Copyright (C) 2005 Mike Wray <mike.wray@hp.com>
+#============================================================================
+# HTTP interface onto xenstore (read-only).
+# Mainly intended for testing.
+
+import os
+import os.path
+
+from xen.web.httpserver import HttpServer, UnixHttpServer
+from xen.web.SrvBase import SrvBase
+from xen.web.SrvDir import SrvDir
+from xen.xend.Args import FormFn
+from xen.xend.xenstore import XenNode
+
+def pathurl(req):
+    url = req.prePathURL()
+    if not url.endswith('/'):
+        url += '/'
+    return url
+    
+def writelist(req, l):
+    req.write('(')
+    for k in l:
+       req.write(' ' + k)
+    req.write(')')
+
+def lsData(dbnode, req, url):
+    v = dbnode.getData()
+    if v is None:
+        req.write('<p>No data')
+    else:
+        req.write('<p>Data: <pre>')
+        req.write(str(v))
+        req.write('</pre>')
+    v = dbnode.getLock()
+    if v is None:
+        req.write("<p>Unlocked")
+    else:
+        req.write("<p>Lock = %s" % v)
+
+def lsChildren(dbnode, req, url):
+    l = dbnode.ls()
+    if l:
+        req.write('<p>Children: <ul>')
+        for key in l:
+            child = dbnode.getChild(key)
+            data = child.getData()
+            if data is None: data = ""
+            req.write('<li><a href="%(url)s%(key)s">%(key)s</a> %(data)s</li>'
+                      % { "url": url, "key": key, "data": data })
+        req.write('</ul>')
+    else:
+        req.write('<p>No children')
+        
+
+class DBDataResource(SrvBase):
+    """Resource for the node data.
+    """
+
+    def __init__(self, dbnode):
+        SrvBase.__init__(self)
+        self.dbnode = dbnode
+
+    def render_GET(self, req):
+        req.write('<html><head></head><body>')
+        self.print_path(req)
+        req.write("<pre>")
+        req.write(self.getData() or self.getNoData())
+        req.write("</pre>")
+        req.write('</body></html>')
+
+    def getContentType(self):
+        # Use content-type from metadata.
+        return "text/plain"
+
+    def getData(self):
+        v = self.dbnode.getData()
+        if v is None: return v
+        return str(v)
+
+    def getNoData(self):
+        return ""
+
+class DBNodeResource(SrvDir):
+    """Resource for a DB node.
+    """
+
+    def __init__(self, dbnode):
+        SrvDir.__init__(self)
+        self.dbnode = dbnode
+
+    def get(self, x):
+        val = None
+        if x == "__data__":
+            val = DBDataResource(self.dbnode)
+        else:
+            if self.dbnode.exists(x):
+                child = self.dbnode.getChild(x, create=False)
+            else:
+                child = None
+            if child is not None:
+                val = DBNodeResource(child)
+        return val
+
+    def render_POST(self, req):
+        return self.perform(req)
+
+    def ls(self, req, use_sxp=0):
+        if use_sxp:
+            writelist(req, self.dbnode.getChildren())
+        else:
+            url = pathurl(req)
+            req.write("<fieldset>")
+            lsData(self.dbnode, req, url)
+            lsChildren(self.dbnode, req, url)
+            req.write("</fieldset>")
+
+    def form(self, req):
+        url = req.prePathURL()
+        pass
+        
+class DBRootResource(DBNodeResource):
+    """Resource for the root of a DB.
+    """
+
+    def __init__(self):
+        DBNodeResource.__init__(self, XenNode())
+
+def main(argv):
+    root = SrvDir()
+    root.putChild('xenstore', DBRootResource())
+    interface = ''
+    port = 8003
+    server = HttpServer(root=root, interface=interface, port=port)
+    server.run()